package com.aop_for_springboot;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * @ClassName: AopForSpringBoot
 * @Author: ZhangYue26
 * @Description: AOP处理逻辑  TODO 参考于  https://www.cnblogs.com/niaobulashi/p/springboot-aop.html
 * @Date: 2020-11-05
 */
@Component
@Aspect
public class AopForSpringBoot {

    @Autowired
    private SysLogService sysLogService;


    /**
     * 这里我们使用注解的形式
     * 当然，我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method
     * 切点表达式:   execution(...)
     *                                  execution（）	                        表达式的主体；
     *                                  第一个”*“符号	                            表示返回值的类型任意；
     *                                  com.sample.service.impl	                AOP所切的服务的包名，即，我们的业务部分
     *                                  包名后面的”..“	                        表示当前包及子包
     *                                  第二个”*“	                            表示类名，*即所有类。此处可以自定义，下文有举例
     *                                  .*(..)	                                表示任何方法名，括号表示参数，两个点表示任何参数类型
     *
     * 切点表达式函数
     * 1 定义
     *     切点表达式函数由关键字和操作参数组成，如execution("*greetTo(..)")的切点表达式。execution为关键字，
     *     而“*greetTo(..)”为操作参数。在这里，execution代表目标类执行某一方法，而“*greetTo(..)”表示目标方法的匹配模式串，
     *     两者联合起来表示目标类greetTo()方法的连接点。为了方便，execution()称作函数，而匹配串“*greetTo(..)”称作函数的参数。
     * 2 类型
     * ① 方法切点函数，通过描述目标类方法信息定义连接点。execution() 方法入参为方法匹配模式串，例：
     * execution("*greetTo(..)"),表示目标类中的greetTo()方法， @annotation()方法入参为方法注解类名,
     * 例：@annotation(com.taotao.NeedTest)表示任何标注了@NeedTest注解的目标类方法
     * ② 方法入参切点函数,通过描述目标类方法参数的信息定义连接点。arg() @arg()
     * ③目标类切点函数,通过描述目标类类型的信息定义连接点。within() target() @within(),target()
     * ④代理类切点函数,通过描述目标类的代理类的信息定义连接点。this()
     */
    @Pointcut("@annotation(com.aop_for_springboot.SysLog)")
    public void logPointCut() {}


    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        System.out.println("执行Around内----方法前");
        Object result = point.proceed();//这里是使用注解的方法内部逻辑,返回参数为方法执行结果
        System.out.println("执行Around内----方法后");
        long time = System.currentTimeMillis() - beginTime;
        try {
            //实现保存日志逻辑
            saveLog(point, time);
        } catch (Exception e) {

        }
        return result;
    }

    @Before("logPointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("进入到Before");
    }

    @After("logPointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("进入到After");
    }

    @AfterReturning(returning="rvt", pointcut="logPointCut()")
    public void AfterExec(JoinPoint joinPoint,Object rvt){//TODO 这里切记返回的参数名称与@AfterReturning注解中的returning属性的值是相同，否则会运行时异常
        System.out.println("进入到AfterReturning");
    }

    @AfterThrowing(throwing="ex",pointcut="logPointCut()")
    public void doRecoveryActions(Throwable ex){//TODO 这里同理要与@AfterThrowing中的throwing属性值相同
        System.out.println("进入到AfterThrowing");
    }


    /**
      * @Description 假设这里保存日志
      * @author ZhangYue26
      * @date 2020/11/5
      * @param [joinPoint, time]
      * @return void
      **/
    private void saveLog(ProceedingJoinPoint joinPoint, long time) {

        // 获取方法的关键信息，类，包
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取方法签名
        Method method = signature.getMethod();//获取方法
        SysLogEntity sysLogEntity = new SysLogEntity();
        sysLogEntity.setExeuTime(time);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        sysLogEntity.setCreateDate(dateFormat.format(new Date()));
        SysLog sysLog = method.getAnnotation(SysLog.class);
        if(sysLog != null) {
            //注解上的描述
            sysLogEntity.setRemark(sysLog.value());
        }
        //请求的 类名、方法名
        String className = joinPoint.getTarget().getClass().getName();//获取请求的接口类名
        String methodName = signature.getName();//获取请求接口的方法名
        sysLogEntity.setClassName(className);
        sysLogEntity.setMethodName(methodName);
        //请求的参数
        Object[] args = joinPoint.getArgs();//接口方法的参数
        try {
            List<String> list = new ArrayList<String>();
            for (Object o : args) {
                list.add(o.toString());
            }
            sysLogEntity.setParams(list.toString());
        } catch (Exception e){

        }
        sysLogService.save(sysLogEntity);
    }




}
